//--------------------------------------------------
/**
 *
 *      Hand Gesture Recognition System
 *
 */
//--------------------------------------------------
/**
 * //LEARNING CLASS//
 * Librería para el aprendizaje del sistema
 *
 * @author Edwin Cobos & Cesar Murcia
 * @version ManageDataSet_v1.3
 *
 * Revisado: 12/11/11
 */
//--------------------------------------------------
//PACKAGE//
package HGR.Learning;
//IMPORT//
import hmm.Hmm;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;

//--------------------------------------------------
/**
 * Histograms Class Módulo que integra los procesos de aprendizaje del sistema
 */
public class Histograms
{
    //--------------------------------------------------
    /**
     * Distacia mínima para establecer la similaridad entre 2 descriptores
     */
    public static float ThresholdDesc = 0.9f;

    //--------------------------------------------------
    /**
     * Proceso principal de Aprendizaje de acuerdo al árbol de procesos.
     * @param args Argumento Línea de Comando
     */
    public static void main(String[] args) throws UnsupportedEncodingException
    {
        int[] frameCount = new int[312];
        int[][] HistogramsSURF;
        int[][] HistogramsSIFT;
        ArrayList<String> DescriptoresSiftFile = new ArrayList<String>();
        ArrayList<String> DescriptoresSurfFile = new ArrayList<String>();
        ListDir("Descriptors", DescriptoresSiftFile, ".sift",frameCount,0,0);
        ListDir("Descriptors", DescriptoresSurfFile, ".surf",frameCount,0,0);

        ArrayList<InteresPoint> PuntosSIFT = new ArrayList<InteresPoint>();
        ArrayList<InteresPoint> PuntosSURF = new ArrayList<InteresPoint>();
        ArrayList<InteresPoint> FilePoints = new ArrayList<InteresPoint>();

        int counti = 0;
        int Angulos[] = {10,15,30,45,60};
        int t,h,v,u;
        int VideoFCount = 0;
        int Div_Frames = 10;
        int Length_sum =0;
        // Para todos los videos
        for(v=0; v<frameCount.length; v++)
        {
            HistogramsSURF = new int[Angulos.length][];
                for(u=0; u<HistogramsSURF.length; u++){
                    HistogramsSURF[u] = new int[Div_Frames];}

            HistogramsSIFT = new int[Angulos.length][];
                for(u=0; u<HistogramsSIFT.length; u++){
                    HistogramsSIFT[u] = new int[Div_Frames];}

            
            // Por cada Angulos
            for(h=0; h<Angulos.length; h++)
            {                
                //Para todos los frames del video

                Length_sum = 0;
                float[] _HistogramsSURF = new float[360/Angulos[h]];
                float[] _HistogramsSIFT = new float[360/Angulos[h]];

                for(t=0; t<Div_Frames; t++)
                {
                    int Div_Res = frameCount[v]%Div_Frames;
                    int Div_Int = (int)(frameCount[v]/Div_Frames);
                    int[] Length_Histograms = new int[Div_Frames];

                    for(u=0; u<Length_Histograms.length; u++)
                    {
                        Length_Histograms[u] = Div_Int;
                        if(u < Div_Res)
                            Length_Histograms[u] += 1;
                    }
                    
                    for(u=0; u<Length_Histograms[t]; u++)
                    {
                        FilePoints = ReadFilePoints(DescriptoresSurfFile.get(VideoFCount + (Length_sum+u)));
                        if(FilePoints.size() > 0)
                        {
                            Histogram(_HistogramsSURF, PuntosSURF, FilePoints, Angulos[h]);
                            PuntosSURF.clear();
                            PuntosSURF.addAll(FilePoints);                            
                        }

                        FilePoints = ReadFilePoints(DescriptoresSiftFile.get(VideoFCount + (Length_sum+u)));
                        if(FilePoints.size() > 0)
                        {
                            Histogram(_HistogramsSIFT, PuntosSIFT, FilePoints, Angulos[h]);
                            PuntosSIFT.clear();
                            PuntosSIFT.addAll(FilePoints);
                        }
                    }
                    Length_sum += Length_Histograms[t];
                    HistogramsSURF[h][t] = Hmm.PMax(_HistogramsSURF);
                    Arrays.fill(_HistogramsSURF, 0.0f);

                    HistogramsSIFT[h][t] = Hmm.PMax(_HistogramsSIFT);
                    Arrays.fill(_HistogramsSIFT, 0.0f);

                        //PuntosSURF. = FilePoints.clone();
                    
                }
                
                PuntosSURF.clear();
                PuntosSIFT.clear();
            }

            float a = Float.MIN_VALUE;
            VideoFCount += frameCount[v];
            //normalizeHistograms(HistogramsSURF, HistogramsSIFT);
            System.out.println("FIN "+DescriptoresSiftFile.get(counti));
            writeHistograms(HistogramsSURF, HistogramsSIFT, DescriptoresSiftFile.get(counti));

            counti+=frameCount[v];
        }
    }

    //--------------------------------------------------
    /**
     * Normaliza los histogramas en el rango de <b>(0-1)</b>
     * @param HistoSurf Histograma de ángulos para los puntos SURF
     * @param HistoSift Histograma de ángulos para los puntos SIFT
     */
    public static void normalizeHistograms(float[][] HistoSurf, float[][] HistoSift)
    {
        int count = 0;
        int p;
        for(p=0; p<HistoSurf.length; p++)
        {
            count = 0;
            for(int t=0; t<HistoSurf[p].length; t++)
            {
                count += HistoSurf[p][t];
            }
            for(int t=0; t<HistoSurf[p].length; t++)
            {
                HistoSurf[p][t] = HistoSurf[p][t]/count;
            }            
        }

        count = 0;
        for(p=0; p<HistoSift.length; p++)
        {
            count = 0;
            for(int t=0; t<HistoSift[p].length; t++)
            {
                count += HistoSift[p][t];
            }
            for(int t=0; t<HistoSift[p].length; t++)
            {
                HistoSift[p][t] = HistoSift[p][t]/count;
            }
        }
    }

    //--------------------------------------------------
    /**
     * Escribe los histogramas de los puntos SIFT y SURF en un archivo
     * @param HistoSurf Histograma de ángulos para los puntos SURF
     * @param HistoSift Histograma de ángulos para los puntos SIFT
     * @param fileid Numero identificador del archivo
     */
    public static void writeHistograms(int[][] HistoSurf, int[][] HistoSift, String Filename) throws UnsupportedEncodingException
    {
        //Escribir los archivos de los histogramas       

        String[] parts = Filename.split("/");

        String filename = "Histograms/"+parts[1]+".hist";
        try
        {
            PrintWriter out = new PrintWriter(filename, "UTF-8");
            //PrintWriter out = new PrintWriter( new OutputStreamWriter(System.out, "UTF-8"));
            

            out.println("#SURF");
            int p;
            for(p=0; p<HistoSurf.length; p++)
            {
                for(int t=0; t<HistoSurf[p].length; t++)
                {
                    out.print(new String(HistoSurf[p][t]+" "));
                }
                out.print("\n");
            }

            out.println("#SIFT");
            for(p=0; p<HistoSift.length; p++)
            {
                for(int t=0; t<HistoSift[p].length; t++)
                {
                    out.print( new String(HistoSift[p][t]+" "));
                }
                out.print("\n");
            }

            out.flush();
            out.close();
        } 
        //catch(FileNotFoundException e)
        catch(Exception e)
        {
            //IJ.error("SURF: savePointsToFile", e.getMessage());
        }        
    }

    //--------------------------------------------------
    /**
     * Evalúa los descriptores de los puntos comparándolos y estableciendo su relación de
     * acuerdo a su similaridad, determinando el movimiento de los mismos a través del tiempo
     * y elabora un histograma que muestra las transiciones de acuerdo a los ángulos entre los
     * puntos similares.
     * @param Histograms Arreglo donde se guardan los histogramas de acuerdo a los diferentes ángulos establecidos
     * @param Puntos Almacena los puntos de los frames en el tiempo para la comparación
     * @param FPoints Almacena los puntos del frame actual para la comparación     
     * @param DivAngles Número de divisiones de los ángulos que determina el número de muestras en el histograma
     * @param h Número de ángulo actual
     */
    public static void Histogram(float[] Histograms, ArrayList<InteresPoint> Puntos, 
            ArrayList<InteresPoint> FPoints, int DivAngles)
    {

        float D = 0.0f;
        float Best_D = Float.MAX_VALUE;
        float Best_D2 = Float.MAX_VALUE;
        int Best_ind = -1;
        int i;

        float Angulo = 0.0f;
        float difY = 0.0f;
        float difX = 0.0f;

        int LimUp = 0;
        int LimDown = 0;
        int a;
        //Para todos los puntos
        int p;
        if(Puntos.size() > 0)
        {
            for(p=0; p<FPoints.size(); p++)
            {
                D = 0.0f;
                Best_D = Float.MAX_VALUE;
                Best_D2 = Float.MAX_VALUE;
                Best_ind = -1;
                
                for(i=0; i<Puntos.size(); i++)
                {
                    //Calcular la distancia entre descriptores
                    D = kNN(FPoints.get(p).Descriptor, Puntos.get(i).Descriptor);
                    //Determinar la similaridad y asegurar que no hallan falsos positivos a traves de una segunda muestra
                    if(D < Best_D)
                    {
                        Best_D2 = Best_D;
                        Best_D = D;
                        Best_ind = i;
                    }else if(D < Best_D2)
                        Best_D2 = D;
                }

                //Si hay match entre dos puntos calcula el angulo y lo guarda en el histograma correspondiente
                if(Best_ind >= 0 && Best_D2 < Float.MAX_VALUE && Best_D/Best_D2 < ThresholdDesc)
                {
                    Angulo = 0.0f;
                    difY = 0.0f;
                    difX = 0.0f;

                    
                    difX = FPoints.get(p).x - Puntos.get(Best_ind).x;

                    difY = FPoints.get(p).y - Puntos.get(Best_ind).y;

                    //Delta del descriptor 2% del frame
                    int Th_distX = 13;
                    int Th_distY = 10;
                    double dst = Math.sqrt(Math.pow(Th_distX,2) + Math.pow(Th_distY,2));
                    double dst1 = Math.sqrt(Math.pow(difX,2) + Math.pow(difY,2));
                    if(dst>dst1)
                    {
                        break;
                    }
                    
                    Angulo = (float)Math.toDegrees(Math.atan2((difY),(difX)));
                    if(Angulo < 0.0f){
                        Angulo = 360 + Angulo;
                    }

                    LimUp = 0;
                    LimDown = 0;                    
                    for(a=0; a<(360/DivAngles); a++)
                    {
                        if(DivAngles%2 != 0)
                        {
                            LimUp = (int)Math.floor(((DivAngles*(a+1))+DivAngles/2))+1;
                            LimDown = (int)Math.floor((DivAngles/2)+DivAngles*a);
                        }
                        else
                        {
                            LimUp = (DivAngles*(a+1))+DivAngles/2;
                            LimDown = (DivAngles/2)+DivAngles*a;
                        }

                        if((Angulo > LimDown) && Angulo <= LimUp)
                        {                            
                            Histograms[((DivAngles*(a+1))/DivAngles)]++;
                            break;
                        }
                        
                        if(DivAngles%2 != 0)
                        {
                            LimUp = (int)Math.floor((DivAngles/2.0))+1;
                            LimDown = (int)Math.floor(360-(DivAngles/2));
                        }
                        else
                        {
                            LimUp = DivAngles/2;
                            LimDown = 360-(DivAngles/2);
                        }

                        if((Angulo > LimDown) || Angulo <= LimUp )
                        {                         
                            Histograms[0]++;
                            break;
                        }
                    }
                }// FIN Si hay match            
            }// FIN para puntos nuevos
        }// FIN si no hay puntos
    }
    //--------------------------------------------------
    /**
     * Lee los puntos de cada archivo de los frames de un video
     * @param filename Nombre del archivo
     * @param t Número de Frame actual
     * @return <b>ArrayList</b> de los puntos del archivo
     */
    public static ArrayList<InteresPoint> ReadFilePoints(String filename)
    {
        File archivo = null;
        FileReader fr = null;
        BufferedReader br = null;
        int NumPuntos = 0;
        InteresPoint PointTemp = new InteresPoint();
        ArrayList<InteresPoint> Points = new ArrayList<InteresPoint>();
        try
        {
            archivo = new File (filename);
            fr = new FileReader (archivo);
            br = new BufferedReader(fr);
            
            // Lectura del fichero
            String linea;
            String[] part;
            int l = 0;
            //Mientras halla lineas en el archivo
            while((linea=br.readLine())!=null)
            {
                //Almacena el número de puntos
                if(l == 0)
                {                   
                    NumPuntos = Integer.parseInt(linea);                    
                    if(NumPuntos == 0)
                        break;
                }

                //Almacena las coordenadas de los puntos
                if(l%2 != 0 && l != 0)
                {
                    PointTemp = new InteresPoint();
                    part = linea.split(" ");
                    PointTemp.x = (int)Float.parseFloat(part[0]);
                    PointTemp.y = (int)Float.parseFloat(part[1]);                    
                }

                //Almacena el descriptor de cada punto
                if(l%2 == 0 && l != 0)
                {
                    part = linea.split(" ");
                    PointTemp.Descriptor = ArrayStringToFloat(part);
                    Points.add(new InteresPoint(PointTemp));
                }
                l++;
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            //Cerrar el Archivo
            try
            {
                if( null != fr )
                {
                    fr.close();
                }
            }
            catch(Exception e2)
            {
                e2.printStackTrace();
            }
        }

        return Points;        
    }

    //--------------------------------------------------
    /**
     * Enlista los directorios y archivos de los puntos SIFT y SURF de cada frame de los videos
     * @param path Dirección de los archivos
     * @param dirs Lista donde se almacenan las direcciones
     * @param fileType Tipo de archivo que se enlista
     * @param FramesCount Array que almacena el número de frames por cada archivo
     * @param Fi Índice del video
     * @param level Nivel de profundidad de la función
     * @return
     */
    public static int ListDir(String path, ArrayList<String> dirs, String fileType,
            int[] FramesCount, int Fi, int level){
        String t = path;
        File f = new File(t);
        //int  = new int[312];

        if(f.isDirectory())
        {            
            
            String[] fn = f.list();
            if(level > 0)
                FramesCount[Fi] = fn.length/2; //SIFT y SURF
            
            level++;
            int i;
            for(i=0; i< fn.length; i++)
            {
                t = path+"/"+fn[i];
                Fi = ListDir(t, dirs,fileType,FramesCount,Fi, level);
            }
            level--;
            if(level == 1)
                Fi++;
            
        }else if(f.isFile())
        {
            if(path.substring(path.length()-fileType.length(), path.length()).equals(fileType)){                
                dirs.add(path);                
            }
        }

        return Fi;
    }

    //--------------------------------------------------
    /**
     * Convierte un <b>String[]</b> en <b>float[]</b>
     * @param src <b>String[]</b> a convertir
     * @return Array convertido a <b>float[]</b>
     */
    public static float[] ArrayStringToFloat(String[]src)
    {
        float[] _dst = new float[src.length];
        int i;
        for(i=0; i<src.length; i++)
        {
            _dst[i] = Float.parseFloat(src[i]);
        }
        return _dst;
    }

    //--------------------------------------------------
    /**
     * Obtiene la distancia entre dos descriptores utilizando el NEAREST NEIGHBOR METHOD kNN
     * @param desc1 Array del descriptor actual
     * @param desc2 Array del descriptor almacenado
     * @return
     */
    public static float kNN(float[] desc1, float[] desc2)
    {
        float d = 0;
        int i;
        float a;
        for (i = 0; i < desc1.length; ++i )
        {
            a = desc1[ i ] - desc2[ i ];
            d += a * a;
        }

        return ( float )Math.sqrt( d );
    }
}
//--------------------------------------------------
//FIN
//--------------------------------------------------